#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#define CLOSESOCKET closesocket
#else
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <errno.h>
#include <unistd.h>
#define SOCKET int
#define INVALID_SOCKET  (SOCKET)(-1)
#define CLOSESOCKET close
#endif

#include <stdarg.h>
#include <assert.h>
#include <stdio.h>

#ifdef _WIN32
typedef char  int8_t;
typedef short int16_t;
typedef int   int32_t;
typedef long long int64_t;
typedef unsigned char  uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int   uint32_t;
typedef unsigned long long uint64_t;

#endif

#include "memcached/protocol_binary.h"
#include "memmap.h"

static int socktoaddr(const char* host, unsigned short port,
                      struct sockaddr_in *addr)
{
    struct hostent *host_ent;
    memset(addr, 0, sizeof(struct sockaddr_in));
    addr->sin_family = AF_INET;
    addr->sin_port   = htons(port);
    //获得主机信息入口
    if ((host_ent=gethostbyname(host))!=NULL) {
        memcpy(&addr->sin_addr, host_ent->h_addr, host_ent->h_length);
    } else {
        //xxx.xxx.xxx.xxx
        if ((addr->sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
            return -1;
        }
    }
    return 0;
}

static SOCKET connect_to(const char * ip, unsigned short port)
{
    struct sockaddr_in addr;
	SOCKET fd = INVALID_SOCKET;
    if (socktoaddr(ip, port, &addr) != 0) {
        return INVALID_SOCKET;
    }

    fd = socket(PF_INET, SOCK_STREAM, 0);

    if (fd == INVALID_SOCKET) {
        return INVALID_SOCKET;
    }

    if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
        return fd;
    } else {
        CLOSESOCKET(fd);
        return INVALID_SOCKET;
    }
}

struct _MEM_MAP
{
    SOCKET fd;
    char ip[64];
    int port;
};

MEM_MAP * mem_map_create(int port, const char * ip)
{
	SOCKET fd = INVALID_SOCKET;
	MEM_MAP * m = 0;

    if (ip == 0) ip = "127.0.0.1";
    if (port == 0) port = 11211;

    fd = connect_to(ip, port);
    if (fd < 0) {
        return 0;
    }

    m = (MEM_MAP*)malloc(sizeof(MEM_MAP));
    if (m == 0) {
        CLOSESOCKET(fd);
        return 0;
    }

    m->fd = fd;
	strncpy(m->ip, ip, sizeof(m->ip) - 1);
    m->port = port;
    return m;
}

void mem_map_destory(MEM_MAP * m)
{
	if (m) {
		if (m->fd >= 0) {
			CLOSESOCKET(m->fd);
		}
		free(m);
	}
}

int recvn(SOCKET fd, void * buf, int len) 
{
    int recv_len = 0;
    char * ptr = (char*)buf;
    while(recv_len < len) {
        int ret = recv(fd, ptr + recv_len, len - recv_len, 0);
	if (ret == 0) {
		return  recv_len;
	}

        if (ret <= 0) {
#ifdef _WIN32
	    if (WSAGetLastError() == WSAEINTR) { continue; }
#else
            if (errno == EINTR) {continue;}
#endif
            return -1;
        }
	recv_len += ret;
	ptr += ret;
    }
    return recv_len;
}

int send_m(SOCKET fd, ...)
{
#ifndef _WIN32
	struct iovec vec[10];
	ssize_t ret;
#else
	WSABUF DataBuf[10];
	int ret;
	DWORD NumberOfBytesSent;
#endif
	int iCurPos;

	va_list args;
	va_start(args, fd);

	iCurPos = 0;
	while(iCurPos < 10) {
		size_t len = 0;
		const char *  buf = va_arg(args, const char *);
		if (buf == 0) {
			break;
		}

		len = va_arg(args, size_t);
		if (len == 0) {
			break;
		}
#ifdef _WIN32
		DataBuf[iCurPos].len = (unsigned long)len;
		DataBuf[iCurPos].buf = (char*)buf;
#else
		vec[iCurPos].iov_base = (void*)buf;
		vec[iCurPos].iov_len =  len;
#endif
		iCurPos ++;
	}
	if (iCurPos == 0) {
		return 0;
	}

#ifndef _WIN32
	ret = writev(fd, vec, 3);
#else

	ret = WSASend(fd,  DataBuf,  iCurPos, &NumberOfBytesSent,  0,  0, 0);
	if (ret == 0) {
		ret = NumberOfBytesSent;
	} else {
		ret = -1;
	}
#endif
	return ret;
}


int mem_map_set(MEM_MAP * m, const char * key, const void * val, int val_len)
{
	unsigned short key_len = 0;
	int ext_len = 0;
	int ret;
	int status;

    protocol_binary_response_set respond;
    protocol_binary_request_set request;

    if (m == 0 || key == 0 || val == 0) {
        return -1;
    }

    if (m->fd == INVALID_SOCKET) {
        m->fd = connect_to(m->ip, m->port);
    }

    if (m->fd == INVALID_SOCKET) {
        return -1;
    }

    key_len = (unsigned short)strlen(key);

    ext_len = sizeof(request.message.body);
    
    request.message.header.request.magic = PROTOCOL_BINARY_REQ;
    request.message.header.request.opcode = PROTOCOL_BINARY_CMD_SET;
    request.message.header.request.keylen = htons(key_len);
    request.message.header.request.extlen = ext_len;
    request.message.header.request.datatype = PROTOCOL_BINARY_RAW_BYTES;
    request.message.header.request.reserved = 0;
    request.message.header.request.bodylen = htonl(val_len + key_len + ext_len);
    request.message.header.request.opaque = 0;
    request.message.header.request.cas = 0;
    request.message.body.flags = 0;
    request.message.body.expiration = htonl(0);


	ret = send_m(m->fd, &request, (size_t)sizeof(request),
				key, (size_t)key_len,
				val, (size_t)val_len, 0);
    if (ret != sizeof(request) + key_len + val_len) {
        CLOSESOCKET(m->fd);
        m->fd = INVALID_SOCKET;
        return -1;
    }

    if (recvn(m->fd, &(respond.message.header), sizeof(respond.message.header)) <= 0) {
        CLOSESOCKET(m->fd);
        m->fd = INVALID_SOCKET;
        return -1;
    }

    assert(respond.message.header.response.magic == PROTOCOL_BINARY_RES);
    assert(respond.message.header.response.opcode == PROTOCOL_BINARY_CMD_SET);
    assert(respond.message.header.response.keylen == ntohs(0));
    assert(respond.message.header.response.extlen == 0);
    assert(respond.message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES);
    assert(respond.message.header.response.bodylen == ntohl(0));
    //uint32_t opaque;
    //uint64_t cas;

    status = ntohs(respond.message.header.response.status);

    if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
        return 0;
    } else {
        int total_body_len = ntohl(respond.message.header.response.bodylen);
        while(total_body_len > 0) {
            char buf[1024] = {0};
            int recv_len = (total_body_len > 1024) ? 1024 : total_body_len;
            int ret = recv(m->fd, buf, recv_len, 0);
            if (ret <= 0) {
                CLOSESOCKET(m->fd);
                m->fd = INVALID_SOCKET;
                return -1;
            }
            total_body_len -= ret;
        }
        return -1;
    }

    return -1;
}


int mem_map_get(MEM_MAP * m, const char * key, void * val, int len)
{
	unsigned short key_len;
	int ret;
	int status;
	protocol_binary_request_get request;
    protocol_binary_response_get respond;

    if (m == 0 || key == 0 || val == 0) {
        return -1;
    }


    if (m->fd == INVALID_SOCKET) {
        m->fd = connect_to(m->ip, m->port);
    }

    if (m->fd == INVALID_SOCKET) {
        return -1;
    }

    key_len = (unsigned short)strlen(key);

    request.message.header.request.magic = PROTOCOL_BINARY_REQ;
    request.message.header.request.opcode = PROTOCOL_BINARY_CMD_GET;
    request.message.header.request.keylen = htons(key_len);
    request.message.header.request.extlen = 0;
    request.message.header.request.datatype = PROTOCOL_BINARY_RAW_BYTES;
    request.message.header.request.reserved = 0;
    request.message.header.request.bodylen = htonl(key_len);
    request.message.header.request.opaque = 0;
    request.message.header.request.cas = 0;

	ret = send_m(m->fd, &request, (size_t)sizeof(request),
					key, (size_t)key_len, 0);
    if (ret != sizeof(request) + key_len) {
        CLOSESOCKET(m->fd);
        m->fd = INVALID_SOCKET;
        return -1;
    }



    if (recvn(m->fd, &(respond.message.header), sizeof(respond.message.header)) <= 0) {
        CLOSESOCKET(m->fd);
        m->fd = INVALID_SOCKET;
        return -1;
    }

    assert(respond.message.header.response.magic == PROTOCOL_BINARY_RES);
    status = ntohs(respond.message.header.response.status);
    if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
		int total_body_len = ntohl(respond.message.header.response.bodylen);
        int ext_len = respond.message.header.response.extlen;
        int val_len = total_body_len - ext_len;

        assert(respond.message.header.response.opcode == PROTOCOL_BINARY_CMD_GET);
        assert(respond.message.header.response.keylen == ntohs(0));
        assert(respond.message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES);

        if ( recvn(m->fd, &respond.message.body, sizeof(respond.message.body)) < 0) {
                CLOSESOCKET(m->fd);
                m->fd = INVALID_SOCKET;
                return -1;
        }

        //int flags = ntohl(respond.message.body.flags);
        if (val_len > 0) {
            if (recvn(m->fd, val, val_len) < 0) {
                CLOSESOCKET(m->fd);
                m->fd = INVALID_SOCKET;
                return -1;
            }
        }
        return val_len;
    } else {
        int total_body_len = ntohl(respond.message.header.response.bodylen);
        while(total_body_len > 0) {
            char buf[1024] = {0};
            int recv_len = (total_body_len > 1024) ? 1024 : total_body_len;
            int ret = recv(m->fd, buf, recv_len, 0);
            if (ret <= 0) {
                CLOSESOCKET(m->fd);
                m->fd = INVALID_SOCKET;
                return -1;
            }
            total_body_len -= ret;
        }

        if (status == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT) {
            return 0;
        }
        return -1;
    }
}


int mem_map_del(MEM_MAP * m, const char * key)
{
	unsigned short key_len;
	int status;
	int ret;
	protocol_binary_request_delete request;
    protocol_binary_response_delete respond;

    if (m == 0 || key == 0) {
        return -1;
    }

    if (m->fd == INVALID_SOCKET) {
        m->fd = connect_to(m->ip, m->port);
    }

    if (m->fd == INVALID_SOCKET) {
        return -1;
    }

    key_len = (unsigned short)strlen(key);

    request.message.header.request.magic = PROTOCOL_BINARY_REQ;
    request.message.header.request.opcode = PROTOCOL_BINARY_CMD_DELETE;
    request.message.header.request.keylen = htons(key_len);
    request.message.header.request.extlen = 0;
    request.message.header.request.datatype = PROTOCOL_BINARY_RAW_BYTES;
    request.message.header.request.reserved = 0;
    request.message.header.request.bodylen = htonl(key_len);
    request.message.header.request.opaque = 0;
    request.message.header.request.cas = 0;

	ret = send_m(m->fd, &request, (size_t)sizeof(request), key, (size_t)key_len, 0);
    if (ret != sizeof(request) + key_len) {
        CLOSESOCKET(m->fd);
        m->fd = INVALID_SOCKET;
        return -1;
    }


    
    if (recvn(m->fd, &(respond.message.header), sizeof(respond.message.header)) <= 0) {
        CLOSESOCKET(m->fd);
        m->fd = INVALID_SOCKET;
        return -1;
    }

    assert(respond.message.header.response.magic == PROTOCOL_BINARY_RES);
    assert(respond.message.header.response.opcode == PROTOCOL_BINARY_CMD_DELETE);
    assert(respond.message.header.response.keylen == ntohs(0));
    assert(respond.message.header.response.extlen == 0);
    assert(respond.message.header.response.datatype == PROTOCOL_BINARY_RAW_BYTES);
    assert(respond.message.header.response.bodylen == ntohl(0));
    //uint32_t opaque;
    //uint64_t cas;

    status = ntohs(respond.message.header.response.status);
    if (status == PROTOCOL_BINARY_RESPONSE_SUCCESS) {
        return 0;
    } else {
        int total_body_len = ntohl(respond.message.header.response.bodylen);
        while(total_body_len > 0) {
            char buf[1024] = {0};
            int recv_len = (total_body_len > 1024) ? 1024 : total_body_len;
            int ret = recv(m->fd, buf, recv_len, 0);
            if (ret <= 0) {
                CLOSESOCKET(m->fd);
                m->fd = INVALID_SOCKET;
                return -1;
            }
            total_body_len -= ret;
        }
        if (status == PROTOCOL_BINARY_RESPONSE_KEY_ENOENT) {
            return 0;
        }
        return -1;
    }

    return -1;
}


#ifdef _BUILD_TEST

int main(int argc, char * argv[])
{
    MEM_MAP * m = mem_map_create(0, 0);

    if (mem_map_set(m, "1234", "ABCD", 4) != 0) {
        printf("set 1234 to ABCD failed\n");
    } else {
        printf("set 1234 to ABCD success\n");
    }

    if (mem_map_set(m, "1234", "ABCDE", 5) != 0) {
        printf("set 1234 to ABCDE failed\n");
    } else {
        printf("set 1234 to ABCDE success\n");
    }


    if (mem_map_del(m, "1234") != 0) {
        printf("delete 1234 failed\n");
    } else {
        printf("delete 1234 success\n");
    }

    if (mem_map_set(m, "12345", "ABCDE", 5) != 0) {
        printf("set 12345 to ABCDE failed\n");
    } else {
        printf("set 12345 to ABCDE success\n");
    }

    {
        char val[256] = {0};
        int len = sizeof(val);
        switch(mem_map_get(m, "1234", val, len)) {
            case 0:
                printf("1234 no found\n");
                break;
            case -1:
                printf("get 1234 failed\n");
                break;
            default:
                printf("1234 val:%s\n", val);
                break;
        } 
    }
 
    {
        char val[256] = {0};
        int len = sizeof(val);
        switch(mem_map_get(m, "12345", val, len)) {
            case 0:
                printf("12345 no found\n");
                break;
            case -1:
                printf("get 12345 failed\n");
                break;
            default:
                printf("12345 val:%s\n", val);
                break;
        } 
    }
    return 0;
}

#endif
